package jxerializeml.xml.transformers;

import jxerializeml.xml.FieldBean;
import jxerializeml.xml.NodeBean;

/**
 * Contains few transformers, which should fit in the most of situations. If any
 * of these is usable for you, just defined your own.
 * 
 * @author Tomáš Zíma
 */
public enum DefaultTransformers implements InTransformer, OutTransformer {
	/**
	 * <p>
	 *   This transformer produces more "technical" output. Programmers (even
	 *   those who aren't very experienced) won't have problems with reading
	 *   or modifying produced output.
	 * </p>
	 * <p>
	 *   It's not recommended, however, to use this output for ordinary users
	 *   (e.g. in user configuration). They won't understand what the "class"
	 *   and they won't know what are that weird names there.
	 * </p>
	 * <p>
	 *   <b>Example of the produced output</b>:
	 *   <pre>
	 *     {@code
 	 *       <object class="com.project.MyClass" transformer="jxerializeml.xml.transformers.DefaultTransformers$1">
 	 *         <field class="int" name="justNumber" value="-9"/>
 	 *         <field class="boolean" name="featureEnabled" value="false"/>
 	 *         <field class="com.project.AnotherClass" name="subObject">
 	 *           <field class="double" name="pi" value="3.141"/>
 	 *         </field>
 	 *       </object>
	 *     }
	 *   </pre>
	 * </p>
	 */
	TECHNICAL {
		@Override
		public NodeBean transformField(FieldBean fieldBean) {
			NodeBean result = new NodeBean();
			
			result.setTagName("field");
			result.setAttribute("name", fieldBean.getName());
			result.setAttribute("class", fieldBean.getClassName());
			
			if (fieldBean.hasValue()) {
				result.setAttribute("value", fieldBean.getValue());
			}
			
			if (fieldBean.hasLength()) {
				result.setAttribute("length", String.valueOf(fieldBean.getLength()));
			}
			
			return result;
		}

		@Override
		public NodeBean transformRoot(String className) {
			NodeBean result = new NodeBean();

			result.setTagName("object");
			result.setAttribute("class", className);
			result.setAttribute("transformer", this.getClass().getName());
			
			return result;
		}

		@Override
		public NodeBean transformArrayItem(FieldBean fieldBean) {
			NodeBean result = new NodeBean();
			
			result.setTagName("item");
			result.setAttribute("name", fieldBean.getName());
			
			if (fieldBean.hasValue()) {
				result.setAttribute("value", fieldBean.getValue());
			}
			
			return result;
		}
		
		@Override
		public FieldBean transformField(NodeBean nodeBean) {
			FieldBean   result  = new FieldBean();
			String      value   = null;
			String      length  = null;
			
			result.setName(nodeBean.getAttributes().get("name"));
			result.setClassName(nodeBean.getAttributes().get("class"));
			
			if ((value = nodeBean.getAttributes().get("value")) != null) {
				result.setValue(value);
			}
			
			if ((length = nodeBean.getAttributes().get("length")) != null) {
				result.setLength(Integer.valueOf(length));
			}
			
			return result;
		}
		
		@Override
		public FieldBean transformArrayItem(NodeBean nodeBean) {
			FieldBean   result  = new FieldBean();
			String      value   = null;
			
			result.setName(nodeBean.getAttributes().get("name"));
			
			if ((value = nodeBean.getAttributes().get("value")) != null) {
				result.setValue(value);
			}
			
			return result;
		}

		@Override
		public String transformRoot(NodeBean nodeBean) throws InvalidTransformerException {
			String className    = nodeBean.getAttributes().get("class");
			String transformer  = nodeBean.getAttributes().get("transformer");
			
			if (!this.getClass().getName().equals(transformer))  {
				throw new InvalidTransformerException(
					"Class was serialized using different transformer than you've used now."
				);
			}
			
			return className;
		}
		
		@Override
		public InformationType translate(NodeBean nodeBean) {
			if (nodeBean.getTagName().equals("object")) {
				return InformationType.OBJECT;
			}
			else
			if (nodeBean.getTagName().equals("field")) {
				return InformationType.FIELD;
			}
			else
			if (nodeBean.getTagName().equals("item")) {
				return InformationType.ARRAY_ITEM;
			}
			
			return InformationType.UNRECOGNIZED;
		}
	},
	
	/**
	 * <p>
	 *   This transformer produces output, where name of the field
	 *   is contained directly in the tag name. It might be useful
	 *   when serializing the class which contains only primitives,
	 *   so the hierarchy is pretty simple and there won't be much
	 *   indentations in the output.
	 * </p>
	 * <p>
	 *   <b>Example of produced output:</b>
	 *   <pre>
	 *     {@code
 	 *       <com.project.MyClass transformer="jxerializeml.xml.transformers.DefaultTransformers$1">
 	 *         <justNumber class="int" value="-9"/>
 	 *         <featureEnabled class="boolean" value="false"/>
 	 *         <subObject class="com.project.AnotherClass">
 	 *           <pi class="double" value="3.141"/>
 	 *         </field>
 	 *       </object>
	 *     }
	 *   </pre>
	 * </p>
	 */
	DIRECT_NAMING {
		@Override
		public NodeBean transformField(FieldBean fieldBean) {
			NodeBean result = new NodeBean();
			
			result.setTagName(fieldBean.getName());
			result.setAttribute("class", fieldBean.getClassName());
			
			if (fieldBean.hasValue()) {
				result.setAttribute("value", fieldBean.getValue());
			}
			
			if (fieldBean.hasLength()) {
				result.setAttribute("length", String.valueOf(fieldBean.getLength()));
			}
			
			return result;
		}

		@Override
		public NodeBean transformRoot(String className) {
			NodeBean result = new NodeBean();
			
			result.setTagName(className);
			result.setAttribute("transformer", this.getClass().getName());
			
			return result;
		}
		
		@Override
		public NodeBean transformArrayItem(FieldBean fieldBean) {
			NodeBean nodeBean = DefaultTransformers.TECHNICAL.transformArrayItem(fieldBean);
			nodeBean.setAttribute("special", "array");
			
			return nodeBean;
		}		

		@Override
		public FieldBean transformField(NodeBean nodeBean) {
			FieldBean   result  = new FieldBean();
			String      value   = null;
			String      length  = null;
			
			result.setName(nodeBean.getTagName());
			result.setClassName(nodeBean.getAttributes().get("class"));
			
			if ((value = nodeBean.getAttributes().get("value")) != null) {
				result.setValue(value);
			}
			
			if ((length = nodeBean.getAttributes().get("length")) != null) {
				result.setLength(Integer.valueOf(length));
			}
			
			return result;
		}
		
		@Override
		public FieldBean transformArrayItem(NodeBean nodeBean) {
			return DefaultTransformers.TECHNICAL.transformArrayItem(nodeBean);
		}

		@Override
		public String transformRoot(NodeBean nodeBean) throws InvalidTransformerException {
			String className    = nodeBean.getTagName();
			String transformer  = nodeBean.getAttributes().get("transformer");
			
			if (!this.getClass().getName().equals(transformer)) {
				throw new InvalidTransformerException(
					"Class was serialized using different transformer than you've used now."
				);
			}
			
			return className;
		}
		
		@Override
		public InformationType translate(NodeBean nodeBean) {
			if (isClassName(nodeBean.getTagName()) && nodeBean.getAttributes().get("transformer") != null) {
				return InformationType.OBJECT;
			}
			else
			if (nodeBean.getAttributes().get("special") != null) {
				return InformationType.ARRAY_ITEM;
			}
			else {
				return InformationType.FIELD;
			}
		}
		
		private final boolean isClassName(String className) {
			try {
				Class.forName(className);
				return true;
			} catch (ClassNotFoundException e) {
				return false;
			}
		}
	};

	// ---------------------------------------------------------------------------------
	// -------------------------- OutTransformer ---------------------------------------
	// ---------------------------------------------------------------------------------
	@Override public abstract NodeBean			transformField(FieldBean fieldBean);
	@Override public abstract NodeBean			transformRoot(String className);
	@Override public abstract NodeBean			transformArrayItem(FieldBean fieldBean);

	// ---------------------------------------------------------------------------------
	// --------------------------- InTransformer ---------------------------------------
	// ---------------------------------------------------------------------------------
	@Override public abstract FieldBean			transformField(NodeBean nodeBean);
	@Override public abstract FieldBean			transformArrayItem(NodeBean nodeBean);
	@Override public abstract String			transformRoot(NodeBean nodeBean) throws InvalidTransformerException;
	@Override public abstract InformationType	translate(NodeBean nodeBean);
}
